/********************************************************************************
 * Copyright (c) 2011-2017 Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License, Version 2.0 which is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0 
 ********************************************************************************/
"A [[Range]] of adjacent [[Enumerable]] values generated by 
 a [[first]] element, and a strictly positive [[size]]. The 
 range includes all values whose offset from `first` is 
 non-negative and less than the `size`."
see (class Span,
     interface Enumerable)
final serializable
class Measure<Element>(first, size)
        extends Range<Element>()
        given Element satisfies Enumerable<Element> {
    
    "The start of the range."
    shared actual Element first;
    
    "The size of the range."
    shared actual Integer size;
    
    "must be nonempty"
    assert (size > 0);
    
    string => first.string + ":" + size.string;
    
    last => first.neighbour(size - 1);
    
    longerThan(Integer length) => size > length;
    
    shorterThan(Integer length) => size < length;
    
    lastIndex => size - 1;
    
    rest => size > 1 
            then Measure(first.successor, size - 1)
            else [];
    
    getFromFirst(Integer index) 
            => index >= 0 && index < size
            then first.neighbour(index)
            else null;
    
    increasing => true;
    decreasing => false;
    
    iterator()
            => object satisfies Iterator<Element> {
        variable value count = 0;
        variable value current = first;
        shared actual Element|Finished next() {
            if (count >= size) {
                return finished;
            }
            else if (count++ == 0) {
                return current;
            }
            else {
                return ++current;
            }
        }
        string => "(``outer.string``).iterator()";
    };
    
    shared actual 
    {Element+} by(Integer step) {
        "step size must be greater than zero"
        assert (step > 0);
        return step == 1 then this else By(step);
    }
    
    class By(Integer step)
            satisfies {Element+} {
        
        size => 1 + (outer.size - 1) / step;
        
        first => outer.first;
        
        string => "(``outer``).by(``step``)";
        
        iterator() => object
                satisfies Iterator<Element> {
            variable value count = 0;
            variable value current = first;
            shared actual Element|Finished next() {
                if (count >= size) {
                    return finished;
                }
                else if (count++ == 0) {
                    return current;
                }
                else {
                    current = current.neighbour(step);
                    return current;
                }
            }
            string => "``outer``.iterator()";
        };
    }
    
    shifted(Integer shift) 
            => shift == 0 
            then this 
            else Measure(first.neighbour(shift), size);
    
    containsElement(Element x)
            => 0 <= x.offset(first) < size;
    
    shared actual 
    Boolean includesRange(Range<Element> range) {
        switch (range)
        case (is Measure<Element>) {
            value offset = range.first.offset(first);
            return 0 <= offset <= size - range.size;
        }
        case (is Span<Element>) {
            if (range.decreasing) {
                return false;
            } else {
                value offset = range.first.offset(first);
                return 0 <= offset <= size - range.size;
            }
        }
    }
    
    shared actual 
    Boolean equals(Object that) {
        if (is Measure<out Object> that) {
            //optimize for another Measure
            return that.size == size && that.first == first;
        } else if (is Span<out Object> that) {
            return that.increasing &&
                    that.first == first && that.size == size;
        } else {
            //it might be another sort of List
            return super.equals(that);
        }
    }
    
    shared actual 
    Element[] measure(Integer from, Integer length) {
        if (length <= 0) {
            return [];
        } else {
            value len = from + length < size 
                    then length
                    else size - from;
            return Measure(first.neighbour(from), len);
        }
    }
    
    shared actual 
    Element[] span(Integer from, Integer to) {
        if (from <= to) {
            if (to < 0 || from >= size) {
                return [];
            } else {
                value len = to < size 
                        then to - from + 1
                        else size - from;
                return Measure(first.neighbour(from), len);
            }
        } else {
            if (from < 0 || to >= size) {
                return [];
            } else {
                value len = from < size 
                        then from - to + 1
                        else size - to;
                return Measure(first.neighbour(to), len).reversed;
            }
        }
    }
    
    shared actual 
    Element[] spanFrom(Integer from) {
        if (from <= 0) {
            return this;
        } else if (from < size) {
            return Measure(first.neighbour(from), size - from);
        } else {
            return [];
        }
    }
    
    shared actual 
    Element[] spanTo(Integer to) {
        if (to < 0) {
            return [];
        } else if (to < size - 1) {
            return Measure(first, to);
        } else {
            return this;
        }
    }
    
    shared actual void each(void step(Element element)) {
        variable value current = first;
        variable value count = 0;
        while (count++<size) {
            step(current++);
        }
    }
}

"Produces a [[Range]] of adjacent [[Enumerable]] values 
 generated by a [[first]] element, and a strictly positive 
 [[size]], or returns the [[empty sequence|empty]] if 
 `size <= 0`. The range includes all values whose offset 
 from `first` is non-negative and less than the `size`.
 
 More precisely, if `x` and `first` are of `Enumerable` 
 type `X`, and `size` is an integer, then `x in first:size` 
 if and only if `0 <= x.offset(first) < size`.
 
 The _measure operator_ `:` is an abbreviation for
 `measure()`:
 
     for (i in start:size) { ... }
     for (char in '0':10) { ... }
 
 The measure operator accepts the first index and size of 
 the range:
 
     0:5     // [0, 1, 2, 3, 4]
 
 If the size is nonpositive, the range is empty:
 
     0:0     // []
     5:0     // []
     0:-5    // []"
since("1.1.0")
shared Range<Element>|[] measure<Element>
        (Element first, Integer size)
        given Element satisfies Enumerable<Element>
        => size <= 0 then [] else Measure(first, size);
